package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.checkerframework.checker.units.qual.A;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author atguigu-mqx
 */
@Service
public class SearchServiceImpl implements SearchService {

    //  服务层，调用操作es 的客户端！ 远程调用service-product
    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RestHighLevelClient restHighLevelClient;


    @Override
    public void upperGoods(Long skuId) {
        /*
        优化的：异步编排！ 拉姆达表达式！
        1.先给Goods 赋值
        2.保存Goods 到es索引库
        上架  --- 23;
         */
        Goods goods = new Goods();

        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        //  赋值skuInfo 基本信息
        goods.setId(skuInfo.getId());
        //  title === skuName
        goods.setTitle(skuInfo.getSkuName());
        goods.setPrice(skuInfo.getPrice().doubleValue());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        goods.setCreateTime(new Date());

        //  品牌信息：
        BaseTrademark trademark = productFeignClient.getTrademarkById(skuInfo.getTmId());
        goods.setTmId(trademark.getId());
        goods.setTmName(trademark.getTmName());
        goods.setTmLogoUrl(trademark.getLogoUrl());

        //  分类数据：
        BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(categoryView.getCategory1Id());
        goods.setCategory2Id(categoryView.getCategory2Id());
        goods.setCategory3Id(categoryView.getCategory3Id());
        goods.setCategory1Name(categoryView.getCategory1Name());
        goods.setCategory2Name(categoryView.getCategory2Name());
        goods.setCategory3Name(categoryView.getCategory3Name());

        //  平台属性：
        //  skuId=23
        //  根据这个 skuId 来获取到对应的平台属性数据 ：一个平台属性，对应1个平台属性值?
        List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);

        List<SearchAttr> searchAttrList = new ArrayList<>();
        //  普通循环
        for (BaseAttrInfo baseAttrInfo : attrList) {
            SearchAttr searchAttr = new SearchAttr();
            searchAttr.setAttrId(baseAttrInfo.getId());
            searchAttr.setAttrName(baseAttrInfo.getAttrName());
            searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
            searchAttrList.add(searchAttr);
        }
        /*
        private Long attrId;
        private String attrValue;
        private String attrName;
         */
        goods.setAttrs(searchAttrList);
        //  商品上架：
        goodsRepository.save(goods);
    }

    @Override
    public void lowerGoods(Long skuId) {
        //  void deleteById(ID var1);
        goodsRepository.deleteById(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        //  在redis中选择一个数据类型来存储数据！
        //  定义key
        String hotKey = "hotScore";
        //  命令行： zincrby sla 100 atguigu
        //  第一个参数key 第二个是成员， 第三个是步长！
        //  return this.connection.getCluster().zincrby(key, increment, value);
        Double count = this.redisTemplate.opsForZSet().incrementScore(hotKey, "skuId:" + skuId, 1);
        //  判断
        if(count%10==0){
            //  找到原来的对象
            Optional<Goods> optional = this.goodsRepository.findById(skuId);
            Goods goods = optional.get();
            goods.setHotScore(count.longValue());
            //  保存最新数据！
            this.goodsRepository.save(goods);
        }
    }

    @Override
    public SearchResponseVo search(SearchParam searchParam) throws IOException {
        /*
            看API : restHighLevelClient
            1.  需要创建一个带有查询的DSL语句！
            2.  执行查询！
            3.  获取到查询之后的结果 给 SearchResponseVo！
         */
        SearchRequest searchRequest = this.buildQueryDsl(searchParam);

        //  使用高级客户端：
        SearchResponse searchResponse = this.restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //  searchResponse--->SearchResponseVo 前四个属性在 parseSearchResult 方法中赋值！
        SearchResponseVo searchResponseVo = this.parseSearchResult(searchResponse);
        /*
        本质：给属性赋值！
        前四个
        private List<SearchResponseTmVo> trademarkList;
        private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        private List<Goods> goodsList = new ArrayList<>();
        private Long total;//总记录数
        后三个
        private Integer pageSize;//每页显示的内容
        private Integer pageNo;//当前页面
        private Long totalPages;// 总页数据
         */
        searchResponseVo.setPageNo(searchParam.getPageNo());
        searchResponseVo.setPageSize(searchParam.getPageSize());
        //  total = 10条数据  pageSize = 3; totalPages = 4 ;
        //  total = 9条数据  pageSize = 3; totalPages = 3 ;
        //  利用分页公式：
        //  Long totalPages = searchResponseVo.getTotal()%searchParam.getPageSize()==0?searchResponseVo.getTotal()/searchParam.getPageSize():searchResponseVo.getTotal()/searchParam.getPageSize()+1;
        //  在实际工作中总结一个特殊的方法！
        Long totalPages = (searchResponseVo.getTotal()+searchParam.getPageSize()-1)/searchParam.getPageSize();
        searchResponseVo.setTotalPages(totalPages);
        //  返回数据
        return searchResponseVo;
    }

    /**
     * 数据转换
     * @param searchResponse
     * @return
     */
    private SearchResponseVo parseSearchResult(SearchResponse searchResponse) {
        /*
        private List<SearchResponseTmVo> trademarkList;
        private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        private List<Goods> goodsList = new ArrayList<>();
        private Long total;//总记录数
         */
        //  searchResponse 存储着es中的数据集！
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        SearchHits hits = searchResponse.getHits();
        //  获取到总记录数
        searchResponseVo.setTotal(hits.getTotalHits().value);
        //  获取商品记录数
        SearchHit[] subHits = hits.getHits();
        //  声明一个集合来存储数据！
        List<Goods> goodsArrayList = new ArrayList<>();

        if (subHits!=null && subHits.length>0){
            //  循环遍历
            for (SearchHit subHit : subHits) {
                //  获取到Json 数据
                String sourceAsString = subHit.getSourceAsString();
                //  将json 数据转换为Goods
                Goods goods = JSON.parseObject(sourceAsString, Goods.class);
                //  细节： 判断是否有高亮显示！
                if (subHit.getHighlightFields().get("title")!=null){
                    //  获取高亮名称：
                    Text title = subHit.getHighlightFields().get("title").getFragments()[0];
                    goods.setTitle(title.toString());
                }
                //  将查询到的数据添加到集合中！
                goodsArrayList.add(goods);
            }
        }
        //  private List<Goods> goodsList = new ArrayList<>();
        searchResponseVo.setGoodsList(goodsArrayList);

        //  private List<SearchResponseTmVo> trademarkList;
        //  获取品牌数据： 通过聚合中的数据获取！
        Map<String, Aggregation> tmIdAggregationMap = searchResponse.getAggregations().asMap();
        //  获取到品牌Id 聚合数据
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) tmIdAggregationMap.get("tmIdAgg");
        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map((bucket) -> {
            //  创建品牌对象
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            //  本质就是tmId
            String keyAsString = ((Terms.Bucket) bucket).getKeyAsString();
            searchResponseTmVo.setTmId(Long.parseLong(keyAsString));

            //  获取品牌名称：
            ParsedStringTerms tmNameAgg = (ParsedStringTerms) ((Terms.Bucket) bucket).getAggregations().asMap().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);

            //  获取品牌Logo：
            ParsedStringTerms tmLogoUrlAgg = (ParsedStringTerms) ((Terms.Bucket) bucket).getAggregations().asMap().get("tmLogoUrlAgg");
            searchResponseTmVo.setTmLogoUrl(tmLogoUrlAgg.getBuckets().get(0).getKeyAsString());
            //  返回品牌的对象
            return searchResponseTmVo;
        }).collect(Collectors.toList());
        searchResponseVo.setTrademarkList(trademarkList);

        //  品牌：平台属性都是通过聚合之后的数据获取的！
        //  private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        //  平台属性：聚合的数据类型是nested！
        ParsedNested attrAgg = (ParsedNested) tmIdAggregationMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");

        List<SearchResponseAttrVo> attrsList = attrIdAgg.getBuckets().stream().map((bucket) -> {
            //  声明记录平台属性对象
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
            searchResponseAttrVo.setAttrId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());
            //  赋值平台属性名称：
            ParsedStringTerms attrNameAgg = (ParsedStringTerms) ((Terms.Bucket) bucket).getAggregations().asMap().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);

            //  赋值平台属性值名称：
            ParsedStringTerms attrValueAgg = (ParsedStringTerms) ((Terms.Bucket) bucket).getAggregations().asMap().get("attrValueAgg");

            //  定义一个集合来存储valueName
            //  第一种！
            //            List<String> valueNameList = new ArrayList<>();
            //            List<? extends Terms.Bucket> buckets = attrValueAgg.getBuckets();
            //            for (Terms.Bucket bucket1 : buckets) {
            //                String valueName = bucket1.getKeyAsString();
            //                valueNameList.add(valueName);
            //            }
            //  第二种!
            //            List<String> valueNameList = attrValueAgg.getBuckets().stream().map((bucket1) -> {
            //                String valueName = bucket1.getKeyAsString();
            //                return valueName;
            //            }).collect(Collectors.toList());

            //  第三种！
            List<String> valueNameList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            searchResponseAttrVo.setAttrValueList(valueNameList);
            //  返回数据
            return searchResponseAttrVo;
        }).collect(Collectors.toList());

        //  赋值平台属性集合数据！
        searchResponseVo.setAttrsList(attrsList);
        //  返回！
        return searchResponseVo;
    }

    /**
     * 生成searchRequest 带有查询条件的！
     * @param searchParam
     * @return
     */
    private SearchRequest buildQueryDsl(SearchParam searchParam) {
        //  创建查询器：{}
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //  创建bool
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //  创建bool --- filter;
        //  判断分类Id 是否为空！
        if(!StringUtils.isEmpty(searchParam.getCategory1Id())){
            //  filter --- term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",searchParam.getCategory1Id()));
        }
        if(!StringUtils.isEmpty(searchParam.getCategory2Id())){
            //  filter --- term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",searchParam.getCategory2Id()));
        }
        if(!StringUtils.isEmpty(searchParam.getCategory3Id())){
            //  filter --- term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",searchParam.getCategory3Id()));
        }

        //  可以通过品牌Id 进行过滤！ trademark=1:小米
        String trademark = searchParam.getTrademark();
        //  判断
        if (!StringUtils.isEmpty(trademark)){
            //  对数据进行分割
            String[] split = trademark.split(":");
            if (split!=null && split.length==2){
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
            }
        }
        
        //  根据平台属性值过滤：
        String[] props = searchParam.getProps();
        //  判断
        if (props!=null && props.length>0){
            //  循环遍历
            for (String prop : props) {
                //  prop = 23:4G:运行内存 对其进行分割
                String[] split = prop.split(":");
                if (split!=null && split.length==3){
                    //  声明两个bool 对象
                    BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
                    BoolQueryBuilder subQueryBuilder = QueryBuilders.boolQuery();
                    //  构建内部的bool 条件
                    subQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId",split[0]));
                    subQueryBuilder.must(QueryBuilders.termQuery("attrs.attrValue",split[1]));
                    //  外层bool
                    queryBuilder.must(QueryBuilders.nestedQuery("attrs",subQueryBuilder, ScoreMode.None));
                    //  总的过滤
                    boolQueryBuilder.filter(queryBuilder);
                }
            }
        }

        //  根据商品的名称查询！
        String keyword = searchParam.getKeyword();
        if (!StringUtils.isEmpty(keyword)){
            //  进行分词查询！
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",keyword).operator(Operator.AND));
        }
        //  {query --- bool}
        searchSourceBuilder.query(boolQueryBuilder);

        //  设置高亮：
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<span style=color:red>");
        highlightBuilder.postTags("</span>");
        searchSourceBuilder.highlighter(highlightBuilder);

        //  排序：
        //  searchSourceBuilder.sort("hotScore", SortOrder.DESC); 写死了，就按照热度排名排序！
        //  传递的数据格式：order=2:asc order=2:desc  1或2 表示的综合或者价格 asc desc 排序的顺序！
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)){
            //  分割数据：
            String[] split = order.split(":");
            if (split!=null && split.length==2){
                //  searchSourceBuilder.sort("hotScore", SortOrder.DESC);
                //  判断数组的第一个数字是几?
                String field = "";
                switch (split[0]){
                    case "1":
                        field="hotScore";
                        break;
                    case "2":
                        field="price";
                        break;
                }
                //  设置排序
                searchSourceBuilder.sort(field, "asc".equals(split[1])?SortOrder.ASC:SortOrder.DESC);
            }else {
                //  给一个默认排序规则
                searchSourceBuilder.sort("hotScore", SortOrder.DESC);
            }
        }
        //  分页：
        //  从哪一条开始计算！
        //  0,2   2,2
        int from = (searchParam.getPageNo()-1)*searchParam.getPageSize();
        searchSourceBuilder.from(from);
        //  每页默认3条数据
        searchSourceBuilder.size(searchParam.getPageSize());

        //  设置聚合：
        //  品牌聚合！
        searchSourceBuilder.aggregation(AggregationBuilders.terms("tmIdAgg").field("tmId")
            .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));

        //  平台属性聚合！ 区别在于nested 数据类型！
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg","attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                  .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));

        //  还需要写东西！
        //  GET /goods/_search
        SearchRequest searchRequest = new SearchRequest("goods");
        //  设置一下： 需要获取这四个数据！
        searchSourceBuilder.fetchSource(new String[] {"id","defaultImg","title","price"},null);
        //  将编写好的dsl语句放入source！
        searchRequest.source(searchSourceBuilder);
        //  将dsl 语句打印出来！
        System.out.println("DSL:\t"+searchSourceBuilder.toString());
        return searchRequest;
    }
}
